﻿using System;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;

namespace System.Windows.Controls
{
    /// <summary>
    /// Implements a <see cref="System.Windows.Controls.Panel"/> derived control which supports a complete transformation matrix stack and a light-weight system
    /// for invoking animations.
    /// </summary>
    public class AnimatedPanel : Panel
    {
        /// <summary>
        /// Initializes a new instance of the <see cref="AnimatedPanel"/> class.
        /// </summary>
        public AnimatedPanel()
        {
            transGroup = new TransformGroup();

            translate = new TranslateTransform();
            transGroup.Children.Add(translate);

            scale = new ScaleTransform();
            transGroup.Children.Add(scale);

            rotate = new RotateTransform();
            transGroup.Children.Add(rotate);

            skew = new SkewTransform();
            transGroup.Children.Add(skew);

            this.RenderTransform = transGroup;

            story = Animation.CreateStoryboard(TimeSpan.FromSeconds(0.5));

            fadeAnim = Animation.CreateAnimation(story, this, "Opacity", 1, 1);
            story.Children.Add(fadeAnim);

            XAnim = Animation.CreateAnimation(story, translate, "X", 0, 0);
            story.Children.Add(XAnim);

            YAnim = Animation.CreateAnimation(story, translate, "Y", 0, 0);
            story.Children.Add(YAnim);
          
            scaleXAnim = Animation.CreateAnimation(story, scale, "ScaleX", 1, 1);
            story.Children.Add(scaleXAnim);

            scaleYAnim = Animation.CreateAnimation(story, scale, "ScaleY", 1, 1);
            story.Children.Add(scaleYAnim);

            skewXAnim = Animation.CreateAnimation(story, skew, "AngleX", 0, 0);
            story.Children.Add(skewXAnim);

            skewYAnim = Animation.CreateAnimation(story, skew, "AngleY", 0, 0);
            story.Children.Add(skewYAnim);

            rotateAnim = Animation.CreateAnimation(story, rotate, "Angle", 0, 0);
            story.Children.Add(rotateAnim);
            story.Completed += new EventHandler(OnAnimated);

            this.Resources.Add(story);

            this.HiddenState = TransitionState.AsScale(0, 0, 0);
            this.VisibleState = TransitionState.AsScale(1, 1, 1);
        }

        void OnAnimated(object sender, EventArgs e)
        {
            if (!obscuring)
            {
                this.InvalidateArrange();
            }
        }

        private TransformGroup      transGroup;
        private TranslateTransform  translate;
        private ScaleTransform      scale;
        private RotateTransform     rotate;
        private SkewTransform       skew;

        private Storyboard          story;
        private DoubleAnimation     fadeAnim;
        private DoubleAnimation     XAnim;
        private DoubleAnimation     YAnim;
        private DoubleAnimation     scaleXAnim;
        private DoubleAnimation     scaleYAnim;
        private DoubleAnimation     skewXAnim;
        private DoubleAnimation     skewYAnim;
        private DoubleAnimation     rotateAnim;

        private bool                obscuring = false;

        /// <summary>
        /// Identifies the <see cref="HiddenState"/> dependency property.
        /// </summary>
        public static readonly DependencyProperty HiddenStateProperty = RegisterDependencyProperty(typeof(AnimatedPanel), "HiddenState", typeof(TransitionState));

        /// <summary>
        /// Identifies the <see cref="VisibleState"/> dependency property.
        /// </summary>
        public static readonly DependencyProperty VisibleStateProperty = RegisterDependencyProperty(typeof(AnimatedPanel), "VisibleState", typeof(TransitionState));

        #region Dependency Property Helpers

        /// <summary>
        /// A static helper method for registering a <see cref="System.Windows.DependencyProperty"/>.
        /// </summary>
        /// <param name="controlType">The <see cref="System.Type"/> for the control to which the property belongs</param>
        /// <param name="name">The name of the property</param>
        /// <param name="propertyType">The <see cref="System.Type"/> for the value of the property</param>
        /// <returns></returns>
        protected static DependencyProperty RegisterDependencyProperty(Type controlType, string name, Type propertyType)
        {
            DependencyProperty prop = DependencyProperty.Register(name, propertyType, controlType, new PropertyMetadata(new PropertyChangedCallback(OnHandleDependencyPropertyChanged)));
            return prop;
        }

        /// <summary>
        /// A static method for handling <see cref="System.Windows.DependencyProperty"/> changes.
        /// </summary>
        /// <param name="obj">The <see cref="System.Windows.DependencyObject"/> against which the property change should be applied</param>
        /// <param name="e">The <see cref="System.Windows.DependencyPropertyChangedEventArgs"/> event argument</param>
        protected static void OnHandleDependencyPropertyChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
        {
            if (obj is AnimatedPanel)
            {
                ((AnimatedPanel)obj).OnDependencyPropertyChanged(e);
            }
        }

        /// <summary>
        /// A virtual handler for processing <see cref="System.Windows.DependencyProperty"/> changes in derived controls.
        /// </summary>
        /// <param name="e">The <see cref="System.Windows.DependencyPropertyChangedEventArgs"/> event argument</param>
        protected virtual void OnDependencyPropertyChanged(DependencyPropertyChangedEventArgs e)
        {
            if (e.Property == HiddenStateProperty)
            {
                this.HiddenState = (TransitionState)e.NewValue;
            }
            else if (e.Property == VisibleStateProperty)
            {
                this.VisibleState = (TransitionState)e.NewValue;
            }
        }

        #endregion

        /// <summary>
        /// Gets/Sets the animation state for the AnimatedPanel when hidden.
        /// </summary>
        public TransitionState HiddenState
        {
            get { return (TransitionState)GetValue(HiddenStateProperty); }
            set { SetValue(HiddenStateProperty, value); }
        }

        /// <summary>
        /// Gets/Sets the animation state for the AnimatedPanel when visible.
        /// </summary>
        public TransitionState VisibleState
        {
            get { return (TransitionState)GetValue(VisibleStateProperty); }
            set { SetValue(VisibleStateProperty, value); }
        }

        /// <summary>
        /// Called by the container prior to animation of the panel, so that transormation origins can be altered relative to the current size of the panel.
        /// </summary>
        protected virtual void SyncTransformOrigins()
        {
            if (null != this.RenderTransform)
            {
                if (this.RenderTransform is TransformGroup)
                {
                    TransformGroup group = (TransformGroup)this.RenderTransform;
                    foreach (Transform transform in group.Children)
                    {
                        CenterTransform(transform);
                    }
                }
                else
                {
                    CenterTransform(this.RenderTransform);
                }
            }
        }

        private void CenterTransform(Transform transform)
        {
            if (transform is ScaleTransform)
            {
                ((ScaleTransform)transform).CenterX = this.ActualWidth / 2;
                ((ScaleTransform)transform).CenterY = this.ActualHeight / 2;
            }
            else if (transform is RotateTransform)
            {
                ((RotateTransform)transform).CenterX = this.ActualWidth / 2;
                ((RotateTransform)transform).CenterY = this.ActualHeight / 2;
            }
            else if (transform is SkewTransform)
            {
                ((SkewTransform)transform).CenterX = this.ActualWidth / 2;
                ((SkewTransform)transform).CenterY = this.ActualHeight / 2;
            }
        }

        private void ApplyTransitions(TransitionState from, TransitionState to)
        {
            fadeAnim.From = from.Opacity;
            fadeAnim.To = to.Opacity;

            XAnim.From = from.X;
            XAnim.To = to.X;

            YAnim.From = from.Y;
            YAnim.To = from.Y;

            rotateAnim.From = from.Angle;
            rotateAnim.To = to.Angle;

            skewXAnim.From = from.SkewX;
            skewXAnim.To = to.SkewX;

            skewYAnim.From = from.SkewY;
            skewYAnim.To = to.SkewY;

            scaleXAnim.From = from.ScaleX;
            scaleXAnim.To = to.ScaleX;

            scaleYAnim.From = from.ScaleY;
            scaleYAnim.To = to.ScaleY;
        }

        /// <summary>
        /// Hides the <see cref="AnimatedPanel"/> by traversing from the <see cref="VisibleState"/> to the <see cref="HiddenState"/>
        /// </summary>
        public void Obscure()
        {
            SyncTransformOrigins();
            obscuring = true;
            ApplyTransitions(this.VisibleState, this.HiddenState);
            story.Begin();
        }

        /// <summary>
        /// Reveals the <see cref="AnimatedPanel"/> by traversing from the <see cref="HiddenState"/> to the <see cref="VisibleState"/>
        /// </summary>
        public void Reveal()
        {
            SyncTransformOrigins();
            obscuring = false;
            ApplyTransitions(this.HiddenState, this.VisibleState);
            story.Begin();
        }
    }
}
